08 复合类型类型 - 数组与切片的使用和注意事项 -2
- 切片(Slice) 是比数组更加灵活和常用的数据结构。
- 与数组不同,切片的长度是可以动态变化的,它是一种对底层数组的引用,并且比数组更适合在需要处理可变长度的数据时使用。
切片
切片是一个引用类型,可以理解为一个可变长度的数组视图。它有三部分:
- 指向底层数组的指针(point):切片背后有一个底层数组。
- 长度(length):切片当前包含的元素数量。
- 容量(capacity):从切片的起始位置到底层数组的结尾位置的元素总数。
切片的特点:
- 动态大小:切片的长度可以根据需要动态增长或缩减。
- 引用类型:切片是对底层数组的引用,修改切片的元素会影响到底层数组。
- 具有容量(Capacity)和长度(Length):切片除了长度外,还具有容量,表示切片最多可以访问的底层数组的元素数量。
创建切片
声明切片
切片的声明不需要指定长度,只需要指定元素的类型。
go
var slice []int // 声明一个 int 类型的切片
// 示例:直接声明切片
var slice []int
fmt.Printf("slice = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))
// 输出:slice = [], len = 0, cap = 0使用 make() 创建切片
使用内置的 make() 函数创建切片,并指定其长度和容量。
go
slice := make([]int, 3, 5)创建了一个新的切片,并初始化底层数组,它的结构:
3表示切片的长度,即当前切片中有 3 个元素,这些元素默认是 0(int的零值)。5表示切片的容量,即底层数组的大小为 5。
make([]int, 3, 5) 实际上做了如下事情:
- 创建一个底层数组,大小为 5(即
[0, 0, 0, 0, 0])。 - 然后创建一个切片,初始长度为 3,即这个切片只包含底层数组中的前 3 个元素(即
[0, 0, 0]),但切片的容量为 5,这意味着从切片的起点到底层数组的结尾一共有 5 个元素可以使用。
go
slice := make([]int, 5) // 创建一个长度为 5 的切片,容量也是 5,默认值为 0
slice := make([]int, 5, 10) // 创建一个长度为 5,容量为 10 的切片
// 示例:通过 make 创建切片
slice := make([]int, 3, 5)
fmt.Printf("slice = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))
// 输出:slice = [0 0 0], len = 3, cap = 5切片的初始化
切片也可以像数组一样使用字面量进行初始化。
go
slice := []int{1, 2, 3, 4, 5} // 创建一个长度为 5 的切片
// 示例:通过字面量创建切片
slice := []int{1, 2, 3, 4, 5}
fmt.Printf("slice = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))
// 输出:slice = [1 2 3 4 5], len = 5, cap = 5通过数组或已有切片创建切片
你可以通过数组或已有的切片生成一个新的切片。
go
slice[start:end]
// start 是起始索引,end 是结束索引(不包括 end 本身)
// 截取范围:[start, end)
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 创建一个切片,包含 arr 的第 1 到第 3 个元素,即 [2, 3, 4]
// 省略 start 或 end 表示从开头或到结尾
slice := arr[:3] // 等同于 arr[0:3]
slice := arr[2:] // 等同于 arr[2:len(arr)]
// 示例:通过数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:3] // 创建一个切片,包含 arr 的第 1 到第 3 个元素,即 [2, 3]
fmt.Printf("slice = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))
// 输出:slice = [2 3], len = 2, cap = 4
// 使用 arr[1:3] 创建切片,从 arr[1](包含)到 arr[3](不包含)之间的元素,即两个元素 [2, 3],所以 len 是 2
// 切片的容量是从切片的起始位置(arr[1])到底层数组的末尾(arr[4])的元素数量。也就是说,arr[1] 到 arr[4] 包含 4 个元素 [2, 3, 4, 5],因此 cap 是 4切片的长度和容量
go
slice := []int{1, 2, 3, 4, 5}
// len() 获取长度
fmt.Println(len(slice)) // 输出:5
// cap() 获取容量
fmt.Println(cap(slice)) // 输出:5通过截取切片生成的新切片的长度和容量可能会不同。
go
slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:3] // [2, 3]
fmt.Println(len(subSlice)) // 输出:2
fmt.Println(cap(subSlice)) // 输出:4(因为底层数组从索引 1 开始后最多有 4 个元素)使用 append() 动态添加元素
append()函数可以向切片中追加元素。- 如果切片的容量不足以容纳新元素,
append()会分配一个新的底层数组,并将原数据复制到新的数组中。 append()可以追加一个切片到另一个切片。
go
slice := []int{1, 2, 3}
slice = append(s, 4, 5) // 追加 4 和 5
fmt.Println(slice) // 输出:[1, 2, 3, 4, 5]
// 追加一个切片到另一个切片
slice1 := []int{1, 2, 3} // len = 3, cap = 3
slice2 := []int{4, 5}
slice1 = append(slice1, slice2...) // 使用 ... 来解包 slice2,slice1 的 len 变为 5(添加了 slice2 的元素),cap 变为 6(扩容 3*2 = 6)
fmt.Println(slice1) // 输出:[1, 2, 3, 4, 5]
fmt.Println(len(slice1), cap(slice1)) // 输出:5 6修改切片
切片是引用类型,修改切片的元素会影响到底层数组,其他共享该数组的切片也会受到影响。
go
slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:3] // [2, 3]
subSlice[0] = 100
fmt.Println(slice) // 输出:[1, 100, 3, 4, 5],底层数组被修改切片与数组的区别
- 长度可变:数组长度固定,而切片长度可变。
- 值类型 vs 引用类型:数组是值类型,赋值或传递会产生副本;切片是引用类型,指向同一个底层数组。
- 内存使用:切片可以更高效地使用内存,因为它们在操作时不会频繁创建新的数组。
切片拷贝
使用 copy() 函数可以将一个切片的内容复制到另一个切片中。
go
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // 将 src 的内容复制到 dst
fmt.Println(dst) // 输出:[1, 2, 3]总结
- 切片是 Go 中常用的数据结构,用于处理动态大小的数据集合。
- 切片是对底层数组的引用,修改切片会影响到底层数组。
append()和copy()是两个常用的切片操作函数,用于动态添加元素和拷贝数据。